Unix 有两个异步输入(asynchronous input)系统. 一种方法是当输入就绪时发送信号, 另一个是系统当输入被读入时发送信号. UCB 中通过设置文件描述符的 O_ASYNC 位来 实现第一种方法. 第二种方法是 POSIX 标准, 它调用 aio_read.
使用异步 I/O
新版本的反弹程序需要两种信号: SIGIO 和 SIGALRM, 所以要建立两个处理函数. SIGIO 处理函数读入击键并根据读入的数据采取行动. SIGALRM 处理函数驱动动画并 检测碰撞.
方法1: 使用 O_ASYNC
使用 O_ASYNC 需要对原有的弹球做4处改动. 首先建立和设置在键盘输入时被调用的 处理函数. 其次, 使用 fcntl 的 F_SETOWN 命令来告诉内核发送输入通知信号给进程. 其他进程可能也连接到键盘. 第三通过调用 fcntl 来设置文件描述符 0 中的 O_ASYNC 位来打开输入信号. 最后, 循环调用 pause 来等待来自计时器或键盘的信号. 当有一个从键盘来的字符到达, 内核向进程发送 SIGIO 信号. SIGIO 的处理函数使用 标准的 curses 函数 getch 来读入这个字符. 当计时器间隔超时, 内核发送以前已经 处理的 SIGALRM 信号.
/* bounce_async.c * purpose animation with user control, using O_ASYNC on fd * note set_ticker() sends SIGALRM, handler does animation * keyboard sends SIGIO, main only calls pause() * compile cc bounce_async.c set_ticker.c -l curses -o bounce_async */ #include <stdio.h> #include <curses.h> #include <signal.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #define MESSAGE "hello" #define BLANK " " int row = 10; int col = 0; int dir = 1; int delay = 200; int done = 0; int main() { void on_alarm(int); void on_input(int); void enable_kbd_signals(); initscr(); crmode(); noecho(); clear(); signal(SIGIO, on_input); enable_kbd_signals(); signal(SIGALRM, on_alarm); set_ticker(delay); move(row, col); addstr(MESSAGE); while(!done) pause(); endwin(); return 0; } void on_input(int signum) { int c = getch(); if (c == 'Q' || c == EOF) done = 1; else if (c == ' ') dir = -dir; } void on_alarm(int signum) { signal(SIGALRM, on_alarm); mvaddstr(row, col, BLANK); col += dir; mvaddstr(row, col, MESSAGE); refresh(); if (dir == -1 && col <= 0) dir = 1; else if (dir == 1 && col + (int)strlen(MESSAGE) >= COLS) dir = -1; } void enable_kbd_signals() { int fd_flags; fcntl(0, F_SETOWN, getpid()); fd_flags = fcntl(0, F_GETFL); fcntl(0, F_SETFL, (fd_flags | O_ASYNC)); }
方法2: 使用 aio_read
相比设置文件描述符的 O_ASYNC 位, 使用 aio_read 更加灵活, 当然也复杂些. 对于原来的弹球程序做4处改动.
第一, 设置输入被读入时所调用的处理函数 on_input
第二, 设置 struct kbcbuf 中的变量来指名等待什么类型的输入, 当输入发生时产生 什么信号. 除了指定 SIGIO 信号, 可以执行任何信号, 甚至 SIGALRM 或 SIGINT
第三, 通过以上定义的结构体传给 aio_read 来递交输入请求. 和调用一般 read 不同 , aio_read 不会阻塞进程. 相反 aio_read 会在完成时发送信号.
最后, 实现处理函数, 函数通过调用 aio_return 来得到输入的字符.
弹球程序中需要异步读入吗
不. 用户输入阻塞程序, 间隔计时器驱动球移动的模式工作很好. 异步读入的优势在于 程序不用被输入阻塞而开一做些其他什么.
异步输入,视频游戏和操作系统
弹球游戏不需要异步输入, 但操作系统需要. 内核需要运行程序而不能把时间浪费在等 待用户输入上.
内核的异步输入是由硬件实现的, 而进程的异步输入是由软件实现的.
内核的设备驱动代码从输入端读入字符, 然后将读入的字符通过终端驱动进程处理. 如果驱动的文件描述符被设置为异步输入, 内核向进程发送信号. 当进程继续运行时, 控制转移到进程内的信号处理函数.